// TcpServer.hpp
#pragma once
#include <memory>
#include <functional>
#include <unordered_map>
#include <cerrno>
#include "TcpSocket.hpp"
#include "Epoller.hpp"
#include "Comm.hpp"

class Connection;
class TcpServer;
using func_t = std::function<void(std::shared_ptr<Connection>)>;
using except_func = std::function<void(std::weak_ptr<Connection>)>;

uint32_t EVENT_IN = {EPOLLIN | EPOLLET}; // 设为ET模式
uint32_t EVENT_OUT = {EPOLLOUT | EPOLLET};
static const int g_buffer_size = 128;

class Connection
{
public:
    Connection(int sock, std::shared_ptr<TcpServer> tcp_server_ptr)
        : _sock(sock), _tcp_server_ptr(tcp_server_ptr)
    {
    }
    ~Connection()
    {
    }
    void SetHandler(func_t recv_cb, func_t send_cb, func_t except_cb)
    {
        _recv_cb = recv_cb;
        _send_cb = send_cb;
        _except_cb = except_cb;
    }
    int Sockfd() { return _sock; }
    void AppendInBuffer(const std::string &info)
    {
        _inbuffer += info;
    }
    void AppendOutBuffer(const std::string &info)
    {
        _outbuffer += info;
    }
    std::string &Inbuffer()
    {
        return _inbuffer;
    }
    std::string &OutBuffer()
    {
        return _outbuffer;
    }

private:
    int _sock;
    std::string _inbuffer;
    std::string _outbuffer;

public:
    func_t _recv_cb; // 读回调
    func_t _send_cb; // 写回调
    func_t _except_cb;

    // 添加一个回指指针
    // std::weak_ptr<TcpServer> _tcp_server_ptr;  // 用weak_ptr会有bug，编译不能通过
    std::shared_ptr<TcpServer> _tcp_server_ptr;

    std::string _ip;
    uint16_t _port;
};

class TcpServer : public nocopy
{
    static const int num = 64;

public:
    TcpServer(uint16_t port, func_t OnMessage)
        : _port(port), _OnMessage(OnMessage), _quit(true), _epoller_ptr(new Epoller), _listensock_ptr(new TcpSocket)
    {
    }
    ~TcpServer()
    {
    }
    void Init()
    {
        _listensock_ptr->Socket();
        SetNonBlockOrDie(_listensock_ptr->getfd()); // 把listensock设置为非阻塞
        _listensock_ptr->Bind(_port);
        _listensock_ptr->Listen();
        lg(Info, "create listen socker success, fd: %d", _listensock_ptr->getfd());

        // 添加连接
        AddConnection(_listensock_ptr->getfd(), EVENT_IN,
                      std::bind(&TcpServer::Accepter, this, std::placeholders::_1), nullptr, nullptr);
    }
    void Loop()
    {
        _quit = false;
        while (!_quit)
        {
            Dispatcher(-1);
            PrintConnection();
        }
        _quit = true;
    }
    void Dispatcher(int timeout)
    {
        int n = _epoller_ptr->EpollerWait(revs, num, timeout);
        for (int i = 0; i < n; ++i)
        {
            uint32_t events = revs[i].events;
            int sock = revs[i].data.fd;
            // 统一把事件异常转化为读写问题，这样只需要在读写函数里处理所有的异常即可
            // 而读写的异常全部放在_except_cb这一个函数里处理
            if (events & EPOLLERR)
                events |= (EPOLLIN | EPOLLOUT);
            if (events & EPOLLHUP) // 对方连接关闭
                events |= (EPOLLIN | EPOLLOUT);

            if ((events & EPOLLIN) && IsConnectionSafe(sock))
            {
                if (_connections[sock]->_recv_cb)
                    _connections[sock]->_recv_cb(_connections[sock]);
            }
            if ((events & EPOLLOUT) && IsConnectionSafe(sock))
            {
                if (_connections[sock]->_send_cb)
                    _connections[sock]->_send_cb(_connections[sock]);
            }
        }
    }
    // 连接管理器
    void Accepter(std::shared_ptr<Connection> connection)
    {
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sock = ::accept(connection->Sockfd(), (struct sockaddr *)&peer, &len); // 函数前面加::表示用的是原生系统调用接口
            if (sock >= 0)
            {
                uint16_t peerport = ntohs(peer.sin_port);
                char ipbuf[16];
                inet_ntop(AF_INET, &peer.sin_addr, ipbuf, sizeof(ipbuf));
                std::string peerip = ipbuf;
                lg(Debug, "get a new link, client info:[%s:%d], sockfd: %d", peerip.c_str(), peerport, sock);

                // 把sock设置为非阻塞
                SetNonBlockOrDie(sock);
                // 添加连接
                AddConnection(sock, EVENT_IN,
                              std::bind(&TcpServer::Recver, this, std::placeholders::_1),
                              std::bind(&TcpServer::Sender, this, std::placeholders::_1),
                              std::bind(&TcpServer::Excepter, this, std::placeholders::_1),
                              peerip, peerport);
            }
            else // sock<0，连接获取失败
            {
                if (errno == EWOULDBLOCK)
                    break; // 读取完毕
                else if (errno == EINTR)
                    continue; // 信号中断IO，还有数据，继续读
                else
                {
                    lg(Warning, "accept error, %s: %d", strerror(errno), errno);
                    break;
                }
            }
        }
    }
    // 事件管理器
    // Recver不用关心数据的格式，服务器只需要IO数据就可以，报文的格式细节，交给上层处理
    void Recver(std::shared_ptr<Connection> connection)
    {
        int sock = connection->Sockfd();
        char buffer[g_buffer_size];
        while (true)
        {
            memset(buffer, 0, sizeof(buffer));
            int n = recv(sock, buffer, sizeof(buffer) - 1, 0); // 前边已经把文件描述符设置成非阻塞了，所以这里传入0也还是非阻塞
            if (n > 0)
            {
                buffer[n] = 0;
                connection->AppendInBuffer(buffer);
            }
            else if (n == 0)
            {
                lg(Info, "sockfd: %d, client info %s:%d quit...", sock, connection->_ip.c_str(), connection->_port);
                connection->_except_cb(connection);
                break;
            }
            else
            {
                if (errno == EWOULDBLOCK)
                    break;
                else if (errno == EINTR)
                    continue;
                else
                {
                    lg(Warning, "sockfd: %d, client info %s:%d recv error...", sock, connection->_ip.c_str(), connection->_port);
                    connection->_except_cb(connection);
                    break;
                }
            }
        }

        // 此时数据有了，但不一定全，所以上层需要：1.检测，2.如果有完整报文，就处理
        _OnMessage(connection); // 我们读到的sock里所有的数据都保存在connection里
    }
    void Sender(std::shared_ptr<Connection> connection)
    {
        int sock = connection->Sockfd();
        auto &outbuffer = connection->OutBuffer();
        while (true)
        {
            ssize_t n = send(sock, outbuffer.c_str(), outbuffer.size(), 0);
            if (n > 0)
            {
                outbuffer.erase(0, n);
                if (outbuffer.empty())
                    break;
            }
            else if (n == 0)
            {
                return;
            }
            else
            {
                if (errno == EWOULDBLOCK)
                    break;
                else if (errno == EINTR)
                    continue;
                else
                {
                    lg(Warning, "sockfd: %d, client info %s:%d send error...", sock, connection->_ip.c_str(), connection->_port);
                    connection->_except_cb(connection);
                    return;
                }
            }
        }

        if (!outbuffer.empty())
        {
            // 没发完，开启对写事件的关心
            EnableEvent(sock, true, true);
        }
        else
        {
            // 发完了，关闭对写事件的关心
            EnableEvent(sock, true, false);
        }
    }
    void Excepter(std::shared_ptr<Connection> connection)
    {
        int fd = connection->Sockfd();
        lg(Warning, "Excepter handler sockfd: %d, client info %s:%d...",
           fd, connection->_ip.c_str(), connection->_port);

        // 当出现异常时，我们直接关闭fd
        // 1. 移除对特定fd的关心
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL, fd, 0);
        // 2. 关闭异常的文件描述符
        close(fd);
        lg(Debug, "close sockfd: %d", fd);
        // 3. 从unordered_map里移除
        // _connections.erase(fd);
        lg(Debug, "remove sockfd: %d from _connections", fd);
    }
    void EnableEvent(int sock, bool readable, bool writeable)
    {
        uint32_t events = 0;
        events |= ((readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0) | EPOLLET);
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_MOD, sock, events);
    }
    bool IsConnectionSafe(int fd)
    {
        auto iter = _connections.find(fd);
        if (iter == _connections.end())
            return false;
        else
            return true;
    }
    void AddConnection(int sock, uint32_t event, func_t recv_cb, func_t send_cb, func_t except_cb,
                       const std::string &ip = "0.0.0.0", uint16_t port = 0)
    {
        // 1. 当有新文件描述符时，需要给文件描述符建立一个Connection对象，然后将fd和Connection对象添加到unordered_map中，
        // 这样等事件就绪了，它就可以根据Connection对象去调用回调函数，执行对应的方法，
        // 每个文件描述符都会有属于自己的回调，listensock也需要添加到_connections中
        // std::shared_ptr<Connection> new_connection(new Connection(sock, std::shared_ptr<TcpServer>(this)));
        // make_shared直接调用构造函数创建Connection对象
        std::shared_ptr<Connection> new_connection = std::make_shared<Connection>(sock, std::shared_ptr<TcpServer>(this));
        new_connection->SetHandler(recv_cb, send_cb, except_cb);
        new_connection->_ip = ip;
        new_connection->_port = port;

        // 2. 添加到unordered_map
        _connections.insert(std::make_pair(sock, new_connection));

        // 3. 最后，还需要把文件描述符及其对应的事件添加到内核中的epoll模型里
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_ADD, sock, event);

        lg(Debug, "add a new connection success, sockfd is: %d", sock);
    }
    void PrintConnection()
    {
        std::cout << "_connections fd list: ";
        for (auto &connection : _connections)
        {
            std::cout << connection.second->Sockfd() << ", inbuffer: " << connection.second->Inbuffer();
        }
        std::cout << std::endl;
    }

private:
    std::shared_ptr<Epoller> _epoller_ptr;      // 内核
    std::shared_ptr<TcpSocket> _listensock_ptr; // 监听套接字
    // 监听套接字可以移到TcpServer外部，因为listensock也会放在_connections里，它只需要和其它套接字一样通过回调函数处理读事件即可
    // 当有文件描述符就绪时，根据文件描述符找到它所匹配的Connection，然后通过Connection里的回调函数来进行数据读取
    std::unordered_map<int, std::shared_ptr<Connection>> _connections;
    struct epoll_event revs[num]; // 已就绪的事件结构体集合
    uint16_t _port;
    bool _quit;

    // 交给上层，让上层处理信息
    func_t _OnMessage;
};